#!/usr/bin/env python
# -*- coding: UTF-8 -*-
'''
@Project ：V2 
@File    ：DB7.py
@IDE     ：PyCharm 
@Author  ：郭星
@Date    ：2025/9/26 11:57 
'''
# 我如果想用这个模型做车间调度 你能帮我写个遗传算法解决这个问题的代码吗 并且把注释写好点
# !/usr/bin/env python
# -*- coding: UTF-8 -*-
'''
@Project ：V2 
@File    ：DB2_modified.py
@IDE     ：PyCharm 
@Author  ：郭星
@Date    ：2025/9/23 19:24 
'''
import random
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from typing import List, Tuple, Dict

# 设置中文显示
plt.rcParams["font.family"] = ["SimHei", "SimHei", "SimHei"]
plt.rcParams["axes.unicode_minus"] = False  # 解决负号显示问题


class GasSchedulingGA:
    def __init__(self,
                 num_workpieces: int,  # 气瓶数量（订单中的标气瓶数）
                 num_target_components: int,  # 目标组分数量（不含底气）
                 num_batch_inflation_eq: int = 2,  # 批次配气设备数量（如1转24分流器）
                 num_single_inflation_eq: int = 3,  # 单瓶配气设备数量
                 num_analysis_eq_per_component: int = 2,  # 每目标组分分析设备数量
                 population_size: int = 50,
                 generations: int = 100,
                 crossover_rate: float = 0.8,
                 mutation_rate: float = 0.1):
        """初始化标气调度遗传算法"""
        self.num_workpieces = num_workpieces  # 气瓶总数
        self.num_target_components = num_target_components  # 目标组分数量（如A、B、C）
        self.num_batch_inflation_eq = num_batch_inflation_eq  # 批次配气设备数
        self.num_single_inflation_eq = num_single_inflation_eq  # 单瓶配气设备数
        self.total_inflation_eq = num_batch_inflation_eq + num_single_inflation_eq  # 总充气设备数
        self.num_analysis_eq_per_component = num_analysis_eq_per_component

        # 遗传算法参数保留
        self.population_size = population_size
        self.generations = generations
        self.crossover_rate = crossover_rate
        self.mutation_rate = mutation_rate

        # 标气核心参数初始化
        self.carrier_gas = "氮气"  # 底气（默认氮气，可修改）
        self.all_components = [self.carrier_gas] + [f"组分{chr(65 + i)}" for i in range(num_target_components)]
        self.num_all_components = len(self.all_components)  # 总组分数（底气+目标）

        # 加工时间初始化
        self.inflation_time_per_comp = {
            self.carrier_gas: np.random.randint(5, 8, size=num_workpieces),  # 底气充气时间（每瓶）
            **{comp: np.random.randint(8, 15, size=num_workpieces) for comp in self.all_components[1:]}  # 目标组分充气时间
        }
        # 分析时间（仅目标组分，底气不分析）
        self.analysis_time = {
            comp: np.random.randint(3, 10, size=num_workpieces)
            for comp in self.all_components[1:]
        }

        # 组分干扰矩阵
        self.interference_matrix = self._create_interference_matrix()

        # 订单批量信息
        self.orders = self._generate_orders(num_orders=5, min_qty=5, max_qty=30)  # 5个订单，每单5-30瓶
        self.workpiece_to_order = self._map_workpiece_to_order()  # 气瓶→所属订单映射

        # 原有参数保留
        self.population = []
        self.generation_best_fitness = []  # 收敛曲线记录

    def _create_interference_matrix(self) -> np.ndarray:
        """生成目标组分间的干扰矩阵（True=有干扰，不可串联；False=无干扰）"""
        num_target = self.num_target_components
        matrix = np.zeros((num_target, num_target), dtype=bool)
        for i in range(num_target):
            for j in range(i + 1, num_target):
                if random.random() < 0.2:  # 20%概率有干扰
                    matrix[i][j] = matrix[j][i] = True
        return matrix

    def _generate_orders(self, num_orders: int, min_qty: int, max_qty: int) -> List[Dict]:
        """生成模拟订单（含订单ID、气瓶数量、截止时间）"""
        orders = []
        total_workpieces = 0
        for order_id in range(num_orders):
            qty = random.randint(min_qty, max_qty)
            if total_workpieces + qty > self.num_workpieces:
                qty = self.num_workpieces - total_workpieces  # 确保总气瓶数匹配
            if qty <= 0:
                break
            # 截止时间：总加工时间预估的1.2-1.5倍
            estimated_total_time = (max(max(t) for t in self.inflation_time_per_comp.values()) +
                                    max(max(t) for t in self.analysis_time.values())) * qty
            due_date = int(estimated_total_time * random.uniform(1.2, 1.5))
            orders.append({
                "order_id": order_id,
                "qty": qty,
                "start_workpiece": total_workpieces,
                "end_workpiece": total_workpieces + qty - 1,
                "due_date": due_date,
                "completion_time": None  # 调度后更新
            })
            total_workpieces += qty
        return orders

    def _map_workpiece_to_order(self) -> List[int]:
        """建立气瓶→所属订单的映射（用于批处理配气判断）"""
        workpiece_to_order = [-1] * self.num_workpieces
        for order in self.orders:
            for wp in range(order["start_workpiece"], order["end_workpiece"] + 1):
                workpiece_to_order[wp] = order["order_id"]
        return workpiece_to_order

    def initialize_chromosome(self) -> Dict:
        """初始化一条染色体，强化设备列表与组分数一致性"""
        # 1. 充气阶段基因
        inflation_order = random.sample(range(self.num_workpieces), self.num_workpieces)
        inflation_eq_type = [random.randint(0, 1) for _ in range(self.num_workpieces)]
        inflation_eq = []
        for eq_type in inflation_eq_type:
            if eq_type == 0:  # 批次设备
                eq_id = random.randint(0, self.num_batch_inflation_eq - 1)
            else:  # 单瓶设备
                eq_id = random.randint(0, self.num_single_inflation_eq - 1)
            inflation_eq.append(eq_id)

        # 2. 分析阶段基因（强化校验）
        analysis_order = random.sample(range(self.num_workpieces), self.num_workpieces)
        component_groups = []
        group_test_order = []
        analysis_eq = []

        for _ in range(self.num_workpieces):
            num_target = self.num_target_components
            max_groups = random.randint(1, num_target)
            groups = [random.randint(0, max_groups - 1) for _ in range(num_target)]
            component_groups.append(groups)

            # 筛选合法串联组（无干扰）
            valid_groups = []
            for group_id in sorted(set(groups)):
                comp_indices = [i for i, g in enumerate(groups) if g == group_id]
                has_interfere = False
                for i in range(len(comp_indices)):
                    for j in range(i + 1, len(comp_indices)):
                        if self.interference_matrix[comp_indices[i]][comp_indices[j]]:
                            has_interfere = True
                            break
                    if has_interfere:
                        break
                if not has_interfere:
                    valid_groups.append(group_id)
                else:
                    # 有干扰则拆分为单组分组
                    for comp_idx in comp_indices:
                        new_group_id = max(valid_groups) + 1 if valid_groups else 0
                        valid_groups.append(new_group_id)
                        groups[comp_idx] = new_group_id

            # 重新整理组测试顺序
            unique_groups = list(sorted(set(groups)))
            random.shuffle(unique_groups)
            group_test_order.append(unique_groups)

            # 生成分析设备选择（确保与组内组分数一致）
            eq_for_workpiece = []
            for group_id in unique_groups:
                comp_indices = [i for i, g in enumerate(groups) if g == group_id]
                # 强制设备列表长度与组分数一致
                eq_for_group = [random.randint(0, self.num_analysis_eq_per_component - 1)
                                for _ in comp_indices]
                # 双重校验长度一致性
                if len(eq_for_group) != len(comp_indices):
                    eq_for_group = [random.randint(0, self.num_analysis_eq_per_component - 1)
                                    for _ in comp_indices]
                eq_for_workpiece.append(eq_for_group)
            analysis_eq.append(eq_for_workpiece)

        return {
            'inflation_order': inflation_order,
            'inflation_eq_type': inflation_eq_type,
            'inflation_eq': inflation_eq,
            'analysis_order': analysis_order,
            'component_groups': component_groups,
            'group_test_order': group_test_order,
            'analysis_eq': analysis_eq
        }

    def initialize_population(self):
        """初始化种群"""
        self.population = [self.initialize_chromosome() for _ in range(self.population_size)]

    def calculate_fitness(self, chromosome: Dict) -> float:
        """计算适应度，添加索引越界保护"""
        # 1. 计算充气阶段时间
        batch_eq_time = [0] * self.num_batch_inflation_eq
        single_eq_time = [0] * self.num_single_inflation_eq
        workpiece_inflation_end = np.zeros(self.num_workpieces)
        processed_orders = set()

        for wp in chromosome['inflation_order']:
            eq_type = chromosome['inflation_eq_type'][wp]
            eq_id = chromosome['inflation_eq'][wp]
            order_id = self.workpiece_to_order[wp]
            order = self.orders[order_id]

            if eq_type == 0 and order_id not in processed_orders:
                # 批处理设备
                carrier_time = max(
                    self.inflation_time_per_comp[self.carrier_gas][order["start_workpiece"]:order["end_workpiece"] + 1])
                target_time = sum(
                    max(self.inflation_time_per_comp[comp][order["start_workpiece"]:order["end_workpiece"] + 1])
                    for comp in self.all_components[1:])
                total_inflate_time = carrier_time + target_time

                start_time = batch_eq_time[eq_id]
                end_time = start_time + total_inflate_time
                batch_eq_time[eq_id] = end_time

                for wp_in_order in range(order["start_workpiece"], order["end_workpiece"] + 1):
                    workpiece_inflation_end[wp_in_order] = end_time
                processed_orders.add(order_id)

            elif eq_type == 1:
                # 单瓶设备
                carrier_time = self.inflation_time_per_comp[self.carrier_gas][wp]
                target_time = sum(self.inflation_time_per_comp[comp][wp] for comp in self.all_components[1:])
                total_inflate_time = carrier_time + target_time

                start_time = single_eq_time[eq_id]
                end_time = start_time + total_inflate_time
                single_eq_time[eq_id] = end_time
                workpiece_inflation_end[wp] = end_time

        # 2. 计算分析阶段时间（添加索引保护）
        analysis_eq_time = {
            comp: [0] * self.num_analysis_eq_per_component
            for comp in self.all_components[1:]
        }
        workpiece_analysis_end = np.zeros(self.num_workpieces)

        for wp in chromosome['analysis_order']:
            # 保护措施：检查工件索引有效性
            if wp < 0 or wp >= self.num_workpieces:
                continue

            start_analysis_time = workpiece_inflation_end[wp]
            groups = chromosome['component_groups'][wp]
            group_order = chromosome['group_test_order'][wp]
            eq_list = chromosome['analysis_eq'][wp]

            current_time = start_analysis_time
            for group_idx, group_id in enumerate(group_order):
                # 保护措施：检查组索引有效性
                if group_idx < 0 or group_idx >= len(eq_list):
                    continue

                comp_indices = [i for i, g in enumerate(groups) if g == group_id]
                comp_names = [self.all_components[1:][i] for i in comp_indices] if comp_indices else []
                eq_for_group = eq_list[group_idx]

                # 核心修复：确保设备列表与组分数一致
                if len(eq_for_group) != len(comp_indices):
                    # 重新生成设备列表以匹配组分数
                    eq_for_group = [random.randint(0, self.num_analysis_eq_per_component - 1)
                                    for _ in comp_indices]
                    # 更新染色体中的设备列表（避免后续再次出错）
                    chromosome['analysis_eq'][wp][group_idx] = eq_for_group

                # 计算组准备时间
                group_ready_time = current_time
                for comp_idx, comp_name in zip(comp_indices, comp_names):
                    # 保护措施：检查组分索引有效性
                    if comp_idx < 0 or comp_idx >= len(eq_for_group):
                        continue

                    eq_id = eq_for_group[comp_idx]
                    # 保护措施：检查设备索引有效性
                    if eq_id < 0 or eq_id >= self.num_analysis_eq_per_component:
                        eq_id = random.randint(0, self.num_analysis_eq_per_component - 1)
                        eq_for_group[comp_idx] = eq_id

                    group_ready_time = max(group_ready_time, analysis_eq_time[comp_name][eq_id])

                # 串联组时长=组内最长分析时间
                max_analyze_time = 0
                if comp_names:  # 避免空列表
                    max_analyze_time = max(self.analysis_time[comp_name][wp] for comp_name in comp_names)
                group_end_time = group_ready_time + max_analyze_time

                # 更新设备时间
                for comp_idx, comp_name in zip(comp_indices, comp_names):
                    if comp_idx < 0 or comp_idx >= len(eq_for_group):
                        continue

                    eq_id = eq_for_group[comp_idx]
                    if eq_id < 0 or eq_id >= self.num_analysis_eq_per_component:
                        continue

                    analysis_eq_time[comp_name][eq_id] = group_end_time

                current_time = group_end_time
                workpiece_analysis_end[wp] = current_time

        # 3. 计算适应度指标
        total_delay = 0
        for order in self.orders:
            order_completion = max(workpiece_analysis_end[order["start_workpiece"]:order["end_workpiece"] + 1])
            order["completion_time"] = order_completion
            delay = max(0, order_completion - order["due_date"])
            total_delay += delay

        total_time = max(workpiece_analysis_end) if self.num_workpieces > 0 else 0

        # 设备利用率
        batch_work_time = sum(batch_eq_time)
        single_work_time = sum(single_eq_time)
        total_inflate_capacity = (self.num_batch_inflation_eq + self.num_single_inflation_eq) * total_time
        inflate_util = (
                                   batch_work_time + single_work_time) / total_inflate_capacity if total_inflate_capacity > 0 else 0

        analysis_work_time = sum(sum(times) for times in analysis_eq_time.values())
        total_analysis_capacity = self.num_target_components * self.num_analysis_eq_per_component * total_time
        analysis_util = analysis_work_time / total_analysis_capacity if total_analysis_capacity > 0 else 0

        avg_util = (inflate_util + analysis_util) / 2

        # 适应度计算
        weight_delay = 10
        weight_util = 5
        fitness_base = total_time + weight_delay * total_delay - weight_util * avg_util
        fitness = 1.0 / fitness_base if fitness_base > 0 else 1e-6

        return fitness

    def get_schedule_details(self, chromosome: Dict) -> Tuple[Dict, Dict, List[Dict]]:
        """获取调度详细信息，添加索引保护"""
        # 1. 充气阶段详情
        inflation_details = {
            "batch": {eq_id: [] for eq_id in range(self.num_batch_inflation_eq)},
            "single": {eq_id: [] for eq_id in range(self.num_single_inflation_eq)}
        }
        batch_eq_time = [0] * self.num_batch_inflation_eq
        single_eq_time = [0] * self.num_single_inflation_eq
        workpiece_inflation_end = np.zeros(self.num_workpieces)
        processed_orders = set()

        for wp in chromosome['inflation_order']:
            if wp < 0 or wp >= self.num_workpieces:
                continue

            eq_type = chromosome['inflation_eq_type'][wp]
            eq_id = chromosome['inflation_eq'][wp]
            order_id = self.workpiece_to_order[wp]
            if order_id < 0 or order_id >= len(self.orders):
                continue
            order = self.orders[order_id]

            if eq_type == 0 and order_id not in processed_orders:
                carrier_time = max(
                    self.inflation_time_per_comp[self.carrier_gas][order["start_workpiece"]:order["end_workpiece"] + 1])
                target_time = sum(
                    max(self.inflation_time_per_comp[comp][order["start_workpiece"]:order["end_workpiece"] + 1])
                    for comp in self.all_components[1:])
                total_inflate_time = carrier_time + target_time

                start_time = batch_eq_time[eq_id]
                end_time = start_time + total_inflate_time
                batch_eq_time[eq_id] = end_time

                inflation_details["batch"][eq_id].append({
                    "type": "batch",
                    "order_id": order_id,
                    "workpieces": f"{order['start_workpiece']}-{order['end_workpiece']}",
                    "start": start_time,
                    "end": end_time,
                    "duration": total_inflate_time,
                    "components": f"{self.carrier_gas} + {', '.join(self.all_components[1:])}"
                })

                for wp_in_order in range(order["start_workpiece"], order["end_workpiece"] + 1):
                    workpiece_inflation_end[wp_in_order] = end_time
                processed_orders.add(order_id)

            elif eq_type == 1:
                carrier_time = self.inflation_time_per_comp[self.carrier_gas][wp]
                target_time = sum(self.inflation_time_per_comp[comp][wp] for comp in self.all_components[1:])
                total_inflate_time = carrier_time + target_time

                start_time = single_eq_time[eq_id]
                end_time = start_time + total_inflate_time
                single_eq_time[eq_id] = end_time

                inflation_details["single"][eq_id].append({
                    "type": "single",
                    "workpiece": wp,
                    "order_id": order_id,
                    "start": start_time,
                    "end": end_time,
                    "duration": total_inflate_time,
                    "components": f"{self.carrier_gas} + {', '.join(self.all_components[1:])}"
                })

                workpiece_inflation_end[wp] = end_time

        # 2. 分析阶段详情
        analysis_details = {
            comp: {eq_id: [] for eq_id in range(self.num_analysis_eq_per_component)}
            for comp in self.all_components[1:]
        }
        analysis_eq_time = {
            comp: [0] * self.num_analysis_eq_per_component
            for comp in self.all_components[1:]
        }
        workpiece_analysis_end = np.zeros(self.num_workpieces)

        for wp in chromosome['analysis_order']:
            if wp < 0 or wp >= self.num_workpieces:
                continue

            start_analysis_time = workpiece_inflation_end[wp]
            groups = chromosome['component_groups'][wp]
            group_order = chromosome['group_test_order'][wp]
            eq_list = chromosome['analysis_eq'][wp]
            order_id = self.workpiece_to_order[wp]

            current_time = start_analysis_time
            for group_idx, group_id in enumerate(group_order):
                if group_idx < 0 or group_idx >= len(eq_list):
                    continue

                comp_indices = [i for i, g in enumerate(groups) if g == group_id]
                comp_names = [self.all_components[1:][i] for i in comp_indices] if comp_indices else []
                eq_for_group = eq_list[group_idx]

                # 确保设备列表与组分数一致
                if len(eq_for_group) != len(comp_indices):
                    eq_for_group = [random.randint(0, self.num_analysis_eq_per_component - 1)
                                    for _ in comp_indices]

                # 检查组内干扰
                has_interfere = False
                for i in range(len(comp_indices)):
                    for j in range(i + 1, len(comp_indices)):
                        if self.interference_matrix[comp_indices[i]][comp_indices[j]]:
                            has_interfere = True
                            break
                    if has_interfere:
                        break

                # 组准备时间
                group_ready_time = current_time
                for comp_idx, comp_name in zip(comp_indices, comp_names):
                    if comp_idx < 0 or comp_idx >= len(eq_for_group):
                        continue

                    eq_id = eq_for_group[comp_idx]
                    if eq_id < 0 or eq_id >= self.num_analysis_eq_per_component:
                        eq_id = random.randint(0, self.num_analysis_eq_per_component - 1)

                    group_ready_time = max(group_ready_time, analysis_eq_time[comp_name][eq_id])

                # 串联时长=最长组分时间
                max_analyze_time = 0
                if comp_names:
                    max_analyze_time = max(self.analysis_time[comp_name][wp] for comp_name in comp_names)
                group_end_time = group_ready_time + max_analyze_time

                # 记录分析任务详情
                for comp_idx, comp_name in zip(comp_indices, comp_names):
                    if comp_idx < 0 or comp_idx >= len(eq_for_group):
                        continue

                    eq_id = eq_for_group[comp_idx]
                    if eq_id < 0 or eq_id >= self.num_analysis_eq_per_component:
                        continue

                    analysis_details[comp_name][eq_id].append({
                        "workpiece": wp,
                        "order_id": order_id,
                        "group_id": group_id,
                        "has_interfere": has_interfere,
                        "start": group_ready_time,
                        "end": group_end_time,
                        "max_duration": max_analyze_time,
                        "individual_duration": self.analysis_time[comp_name][wp]
                    })
                    analysis_eq_time[comp_name][eq_id] = group_end_time

                current_time = group_end_time
                workpiece_analysis_end[wp] = current_time

        # 3. 订单完成详情
        order_details = []
        for order in self.orders:
            order_completion = max(workpiece_analysis_end[order["start_workpiece"]:order["end_workpiece"] + 1])
            delay = max(0, order_completion - order["due_date"])
            order_details.append({
                "order_id": order["order_id"],
                "qty": order["qty"],
                "due_date": order["due_date"],
                "completion_time": order_completion,
                "delay": delay,
                "status": "按时" if delay == 0 else "延迟"
            })

        return inflation_details, analysis_details, order_details

    def select_parents(self, fitness_values: List[float]) -> Tuple[Dict, Dict]:
        """轮盘赌选择父母"""
        total_fitness = sum(fitness_values)
        if total_fitness == 0:
            probabilities = [1 / self.population_size] * self.population_size
        else:
            probabilities = [f / total_fitness for f in fitness_values]

        parent1_idx = np.random.choice(self.population_size, p=probabilities)
        parent2_idx = np.random.choice(self.population_size, p=probabilities)
        while parent1_idx == parent2_idx:
            parent2_idx = np.random.choice(self.population_size, p=probabilities)

        return self.population[parent1_idx], self.population[parent2_idx]

    def crossover(self, parent1: Dict, parent2: Dict) -> Tuple[Dict, Dict]:
        """交叉操作，强化设备列表与组分数一致性"""
        if random.random() > self.crossover_rate:
            return parent1.copy(), parent2.copy()

        # 顺序交叉函数
        def order_crossover(order1, order2):
            size = len(order1)
            a, b = random.sample(range(size), 2)
            if a > b:
                a, b = b, a

            child1 = [-1] * size
            child2 = [-1] * size
            child1[a:b + 1] = order1[a:b + 1]
            child2[a:b + 1] = order2[a:b + 1]

            def fill_child(child, parent, start, end):
                ptr = (end + 1) % size
                for i in range(end + 1, end + 1 + size):
                    current = parent[i % size]
                    if current not in child:
                        child[ptr] = current
                        ptr = (ptr + 1) % size
                return child

            child1 = fill_child(child1, order2, a, b)
            child2 = fill_child(child2, order1, a, b)
            return child1, child2

        # 交叉充气/分析顺序
        child1_inflate_order, child2_inflate_order = order_crossover(
            parent1['inflation_order'], parent2['inflation_order']
        )
        child1_analysis_order, child2_analysis_order = order_crossover(
            parent1['analysis_order'], parent2['analysis_order']
        )

        # 深拷贝染色体
        child1 = {
            'inflation_order': child1_inflate_order,
            'inflation_eq_type': parent1['inflation_eq_type'].copy(),
            'inflation_eq': parent1['inflation_eq'].copy(),
            'analysis_order': child1_analysis_order,
            'component_groups': [g.copy() for g in parent1['component_groups']],
            'group_test_order': [g.copy() for g in parent1['group_test_order']],
            'analysis_eq': [g.copy() for g in parent1['analysis_eq']]
        }
        for i in range(len(child1['analysis_eq'])):
            child1['analysis_eq'][i] = [e.copy() for e in child1['analysis_eq'][i]]

        child2 = {
            'inflation_order': child2_inflate_order,
            'inflation_eq_type': parent2['inflation_eq_type'].copy(),
            'inflation_eq': parent2['inflation_eq'].copy(),
            'analysis_order': child2_analysis_order,
            'component_groups': [g.copy() for g in parent2['component_groups']],
            'group_test_order': [g.copy() for g in parent2['group_test_order']],
            'analysis_eq': [g.copy() for g in parent2['analysis_eq']]
        }
        for i in range(len(child2['analysis_eq'])):
            child2['analysis_eq'][i] = [e.copy() for e in child2['analysis_eq'][i]]

        # 交叉充气设备类型和ID
        for i in range(self.num_workpieces):
            if random.random() < 0.5:
                child1['inflation_eq_type'][i] = parent2['inflation_eq_type'][i]
                child1['inflation_eq'][i] = parent2['inflation_eq'][i]
                child2['inflation_eq_type'][i] = parent1['inflation_eq_type'][i]
                child2['inflation_eq'][i] = parent1['inflation_eq'][i]

        # 组分分组交叉（强化一致性校验）
        for i in range(self.num_workpieces):
            if random.random() < 0.5:
                # 交叉组分分组
                child1['component_groups'][i] = parent2['component_groups'][i].copy()
                child2['component_groups'][i] = parent1['component_groups'][i].copy()

                # 重新校验干扰并调整
                for child in [child1, child2]:
                    groups = child['component_groups'][i]
                    new_groups = groups.copy()
                    for group_id in set(new_groups):
                        comp_indices = [j for j, g in enumerate(new_groups) if g == group_id]
                        has_interfere = False
                        for a in range(len(comp_indices)):
                            for b in range(a + 1, len(comp_indices)):
                                if self.interference_matrix[comp_indices[a]][comp_indices[b]]:
                                    has_interfere = True
                                    break
                            if has_interfere:
                                break
                        if has_interfere:
                            new_group_id = max(set(new_groups)) + 1 if new_groups else 0
                            for idx in comp_indices:
                                new_groups[idx] = new_group_id
                                new_group_id += 1
                    child['component_groups'][i] = new_groups

                    # 重新生成组测试顺序和设备（确保长度一致）
                    unique_groups = list(sorted(set(new_groups)))
                    random.shuffle(unique_groups)
                    child['group_test_order'][i] = unique_groups

                    new_eq = []
                    for group_id in unique_groups:
                        comp_indices = [j for j, g in enumerate(new_groups) if g == group_id]
                        # 强制设备列表长度与组分数一致
                        eq_for_group = [random.randint(0, self.num_analysis_eq_per_component - 1)
                                        for _ in comp_indices]
                        new_eq.append(eq_for_group)
                    child['analysis_eq'][i] = new_eq

        return child1, child2

    def mutate(self, chromosome: Dict) -> Dict:
        """变异操作，强化设备列表与组分数一致性"""
        # 深拷贝
        mutated = {
            'inflation_order': chromosome['inflation_order'].copy(),
            'inflation_eq_type': chromosome['inflation_eq_type'].copy(),
            'inflation_eq': chromosome['inflation_eq'].copy(),
            'analysis_order': chromosome['analysis_order'].copy(),
            'component_groups': [g.copy() for g in chromosome['component_groups']],
            'group_test_order': [g.copy() for g in chromosome['group_test_order']],
            'analysis_eq': [g.copy() for g in chromosome['analysis_eq']]
        }
        for i in range(len(mutated['analysis_eq'])):
            mutated['analysis_eq'][i] = [e.copy() for e in mutated['analysis_eq'][i]]

        # 1. 充气顺序变异
        if random.random() < self.mutation_rate:
            idx1, idx2 = random.sample(range(self.num_workpieces), 2)
            mutated['inflation_order'][idx1], mutated['inflation_order'][idx2] = \
                mutated['inflation_order'][idx2], mutated['inflation_order'][idx1]

        # 2. 充气设备类型变异
        if random.random() < self.mutation_rate:
            idx = random.randint(0, self.num_workpieces - 1)
            mutated['inflation_eq_type'][idx] = 1 - mutated['inflation_eq_type'][idx]  # 0↔1切换
            # 同步更新设备ID
            if mutated['inflation_eq_type'][idx] == 0:
                mutated['inflation_eq'][idx] = random.randint(0, self.num_batch_inflation_eq - 1)
            else:
                mutated['inflation_eq'][idx] = random.randint(0, self.num_single_inflation_eq - 1)

        # 3. 分析顺序变异
        if random.random() < self.mutation_rate:
            idx1, idx2 = random.sample(range(self.num_workpieces), 2)
            mutated['analysis_order'][idx1], mutated['analysis_order'][idx2] = \
                mutated['analysis_order'][idx2], mutated['analysis_order'][idx1]

        # 4. 组分分组变异（强化一致性）
        for i in range(self.num_workpieces):
            if random.random() < self.mutation_rate:
                comp_idx = random.randint(0, self.num_target_components - 1)
                current_groups = mutated['component_groups'][i]
                current_group = current_groups[comp_idx]
                # 切换到其他组
                other_groups = [g for g in set(current_groups) if g != current_group]
                if other_groups:
                    new_group = random.choice(other_groups)
                else:
                    new_group = 1 if current_group == 0 else 0
                current_groups[comp_idx] = new_group

                # 重新校验干扰
                new_groups = current_groups.copy()
                for group_id in set(new_groups):
                    comp_indices = [j for j, g in enumerate(new_groups) if g == group_id]
                    has_interfere = False
                    for a in range(len(comp_indices)):
                        for b in range(a + 1, len(comp_indices)):
                            if self.interference_matrix[comp_indices[a]][comp_indices[b]]:
                                has_interfere = True
                                break
                        if has_interfere:
                            break
                    if has_interfere:
                        new_group_id = max(set(new_groups)) + 1 if new_groups else 0
                        for idx in comp_indices:
                            new_groups[idx] = new_group_id
                            new_group_id += 1
                mutated['component_groups'][i] = new_groups

                # 重新生成组测试顺序和设备（确保长度一致）
                unique_groups = list(sorted(set(new_groups)))
                random.shuffle(unique_groups)
                mutated['group_test_order'][i] = unique_groups

                new_eq = []
                for group_id in unique_groups:
                    comp_indices = [j for j, g in enumerate(new_groups) if g == group_id]
                    # 强制设备列表长度与组分数一致
                    eq_for_group = [random.randint(0, self.num_analysis_eq_per_component - 1)
                                    for _ in comp_indices]
                    new_eq.append(eq_for_group)
                mutated['analysis_eq'][i] = new_eq

        return mutated

    def evolve(self) -> Tuple[Dict, float]:
        """执行遗传算法进化过程"""
        self.initialize_population()
        self.generation_best_fitness = []
        best_fitness = 0
        best_chromosome = None

        for generation in range(self.generations):
            try:
                fitness_values = [self.calculate_fitness(chrom) for chrom in self.population]
            except Exception as e:
                print(f"计算适应度时出错: {e}")
                # 出错时重新生成种群
                self.initialize_population()
                fitness_values = [self.calculate_fitness(chrom) for chrom in self.population]

            current_best_idx = np.argmax(fitness_values)
            current_best_fitness = fitness_values[current_best_idx]
            current_best_chromosome = self.population[current_best_idx]

            self.generation_best_fitness.append(current_best_fitness)

            if current_best_fitness > best_fitness:
                best_fitness = current_best_fitness
                best_chromosome = current_best_chromosome

            # 每10代打印进度
            if generation % 10 == 0:
                _, _, order_details = self.get_schedule_details(current_best_chromosome)
                total_delay = sum(od["delay"] for od in order_details)
                print(f"Generation {generation:3d}: "
                      f"Best Fitness = {current_best_fitness:.4f}, "
                      f"Total Time = {1 / current_best_fitness:.2f}, "
                      f"Total Delay = {total_delay:.0f}")

            # 生成新一代
            new_population = []
            elite_size = max(1, int(self.population_size * 0.1))
            elite_indices = np.argsort(fitness_values)[-elite_size:]
            for idx in elite_indices:
                new_population.append(self.population[idx])

            while len(new_population) < self.population_size:
                parent1, parent2 = self.select_parents(fitness_values)
                child1, child2 = self.crossover(parent1, parent2)
                child1 = self.mutate(child1)
                child2 = self.mutate(child2)
                new_population.append(child1)
                if len(new_population) < self.population_size:
                    new_population.append(child2)

            self.population = new_population

        return best_chromosome, best_fitness

    def plot_convergence_curve(self):
        """绘制收敛曲线"""
        plt.figure(figsize=(10, 6))
        plt.plot(range(1, self.generations + 1), self.generation_best_fitness, 'b-', linewidth=2)
        plt.xlabel('进化代数')
        plt.ylabel('最佳适应度')
        plt.title('遗传算法收敛曲线（标气调度）')
        plt.grid(True, linestyle='--', alpha=0.7)
        plt.tight_layout()
        plt.show()

    def plot_gantt_chart(self, chromosome: Dict):
        """绘制甘特图展示调度方案"""
        inflation_details, analysis_details, order_details = self.get_schedule_details(chromosome)
        colors = plt.cm.tab10(np.linspace(0, 1, len(self.orders)))  # 按订单配色

        # 创建画布
        fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(14, 12 + self.num_target_components * 1.2))
        fig.suptitle('标气生产调度甘特图', fontsize=16, fontweight='bold')

        # 子图1：批次配气设备甘特图
        ax1.set_title('1. 批次配气设备（多瓶并行）', fontsize=12, fontweight='bold')
        y_ticks = []
        y_labels = []
        y_pos = 1

        for eq_id in sorted(inflation_details["batch"].keys()):
            tasks = inflation_details["batch"][eq_id]
            if not tasks:
                continue
            y_ticks.append(y_pos)
            y_labels.append(f'批次设备{eq_id}')

            for task in tasks:
                order_id = task["order_id"]
                start = task["start"]
                end = task["end"]
                ax1.barh(y_pos, end - start, left=start, height=0.6,
                         color=colors[order_id], edgecolor='black', alpha=0.8)
                label = f'订单{order_id}\n({task["workpieces"]}瓶)'
                ax1.text(start + (end - start) / 2, y_pos, label,
                         ha='center', va='center', color='white', fontweight='bold', fontsize=8)
            y_pos += 1

        ax1.set_yticks(y_ticks)
        ax1.set_yticklabels(y_labels)
        ax1.set_xlabel('时间（分钟）')
        ax1.grid(True, axis='x', linestyle='--', alpha=0.5)

        # 子图2：单瓶配气设备甘特图
        ax2.set_title('2. 单瓶配气设备（逐个充气）', fontsize=12, fontweight='bold')
        y_ticks = []
        y_labels = []
        y_pos = 1

        for eq_id in sorted(inflation_details["single"].keys()):
            tasks = inflation_details["single"][eq_id]
            if not tasks:
                continue
            y_ticks.append(y_pos)
            y_labels.append(f'单瓶设备{eq_id}')

            for task in tasks:
                order_id = task["order_id"]
                wp = task["workpiece"]
                start = task["start"]
                end = task["end"]
                ax2.barh(y_pos, end - start, left=start, height=0.6,
                         color=colors[order_id], edgecolor='black', alpha=0.8)
                label = f'瓶{wp}\n(订单{order_id})'
                ax2.text(start + (end - start) / 2, y_pos, label,
                         ha='center', va='center', color='white', fontweight='bold', fontsize=7)
            y_pos += 1

        ax2.set_yticks(y_ticks)
        ax2.set_yticklabels(y_labels)
        ax2.set_xlabel('时间（分钟）')
        ax2.grid(True, axis='x', linestyle='--', alpha=0.5)

        # 子图3：分析设备甘特图
        ax3.set_title('3. 分析设备（虚线=串联组，红框=有干扰）', fontsize=12, fontweight='bold')
        y_ticks = []
        y_labels = []
        y_pos = 1
        group_positions = {}

        for comp_name in sorted(analysis_details.keys()):
            for eq_id in sorted(analysis_details[comp_name].keys()):
                tasks = analysis_details[comp_name][eq_id]
                if not tasks:
                    continue
                y_ticks.append(y_pos)
                y_labels.append(f'{comp_name}设备{eq_id}')

                for task in tasks:
                    order_id = task["order_id"]
                    wp = task["workpiece"]
                    group_id = task["group_id"]
                    start = task["start"]
                    end = task["end"]
                    has_interfere = task["has_interfere"]

                    bar = ax3.barh(y_pos, end - start, left=start, height=0.6,
                                   color=colors[order_id], edgecolor='black', alpha=0.8)
                    if has_interfere:
                        for rect in bar:
                            rect.set_edgecolor('red')
                            rect.set_linewidth(2)

                    label = f'瓶{wp}\n组{group_id}\n({task["individual_duration"]}min)'
                    ax3.text(start + (end - start) / 2, y_pos, label,
                             ha='center', va='center', color='white', fontweight='bold', fontsize=7)

                    group_key = (wp, group_id)
                    if group_key not in group_positions:
                        group_positions[group_key] = []
                    group_positions[group_key].append((y_pos, start, end))

                y_pos += 1

        # 绘制串联组连接线
        for (wp, group_id), positions in group_positions.items():
            if len(positions) > 1:
                mid_time = (positions[0][1] + positions[0][2]) / 2
                y_min = min(p[0] for p in positions) - 0.3
                y_max = max(p[0] for p in positions) + 0.3
                ax3.plot([mid_time, mid_time], [y_min, y_max], 'k--', linewidth=1.2, alpha=0.7)
                ax3.text(mid_time, y_max + 0.2, f'串联组({wp}-{group_id})',
                         ha='center', va='bottom', fontsize=6, fontweight='bold')

        ax3.set_yticks(y_ticks)
        ax3.set_yticklabels(y_labels)
        ax3.set_xlabel('时间（分钟）')
        ax3.grid(True, axis='x', linestyle='--', alpha=0.5)

        # 添加图例
        legend_items = [mpatches.Patch(color=colors[od["order_id"]], label=f'订单{od["order_id"]}（{od["status"]}）')
                        for od in order_details]
        fig.legend(handles=legend_items, loc='lower center', ncol=len(order_details), bbox_to_anchor=(0.5, 0.01))

        plt.tight_layout(rect=[0, 0.05, 1, 0.95])
        plt.show()

        # 打印订单详情
        print("\n" + "=" * 60)
        print("订单调度结果详情")
        print("=" * 60)
        for od in order_details:
            print(f"订单{od['order_id']:2d} | 数量：{od['qty']:2d}瓶 | 截止时间：{od['due_date']:4d} | "
                  f"完成时间：{od['completion_time']:6.1f} | 延迟：{od['delay']:4.1f} | 状态：{od['status']}")


# 示例用法
if __name__ == "__main__":
    ga = GasSchedulingGA(
        num_workpieces=50,
        num_target_components=4,
        num_batch_inflation_eq=2,
        num_single_inflation_eq=3,
        num_analysis_eq_per_component=2,
        population_size=50,
        generations=100,
        crossover_rate=0.8,
        mutation_rate=0.1
    )

    best_solution, best_fitness = ga.evolve()

    print("\n" + "=" * 60)
    print("最优调度方案核心信息")
    print("=" * 60)
    print(f"总加工时间：{1 / best_fitness:.2f} 分钟")
    print(f"充气顺序（前10个气瓶）：{best_solution['inflation_order'][:10]}...")
    print(f"充气设备类型（前10个，0=批次，1=单瓶）：{best_solution['inflation_eq_type'][:10]}...")
    print(f"分析顺序（前10个气瓶）：{best_solution['analysis_order'][:10]}...")

    ga.plot_convergence_curve()
    ga.plot_gantt_chart(best_solution)
